#!/usr/bin/env python

import os, sys, datetime, re
import xml.dom.minidom
from xml.dom.minidom import Node
from subprocess import *
from time import sleep

def main():
	global config, numCores, executable, configOrder
	
	fileName = "test.xml"
	quit = 0
	config = {}
	configOrder = ["Latency", "FightLength", "NumberOfFights", "MangleBot", "BleedBot", "UseShred", "MinimumRoarPoints", "MaxRoarClip", "BossArmor", "Trinket1Delay", "Trinket2Delay", "HasteRating", "ArPenRating", "HitRating", "ExpertiseRating", "CritRating", "AP", "WeaponAP", "Agi", "Str", "Race", "CritMeta", "RipGlyph", "ShredGlyph", "RoarGlyph", "MangleGlyph", "TwoTSeven", "FourTSeven", "TwoTEight", "FourTEight", "TwoTNine", "FourTNine", "RipIdol", "ShredIdol", "MangleIdol", "MutilationIdol", "Trinket1", "Trinket2", "WeaponEnchant", "StrengthOfEarth", "ImpSoE", "BlessingOfMight", "ImpBoM", "TrueshotAura", "FerociousInspiration", "SwiftRetribution", "Windfury", "ImpWF", "MarkOfTheWild", "ImpMotWBuff", "BlessingOfKings", "HeroicPresence", "Sunder", "FaerieFire", "HeartOfTheCrusader", "BloodFrenzy", "Heroism", "Ferocity", "FeralAggression", "SavageFury", "FeralInstinct", "SharpenedClaws", "PrimalFury", "PrimalPrecision", "PredatoryStrikes", "ShreddingAttacks", "SurvivalOfTheFittest", "HeartOfTheWild", "LeaderOfThePack", "PredatoryInstincts", "Mangle", "ImprovedMangle", "KingOfTheJungle", "RendAndTear", "PrimalGore", "Berserk", "ImpMotWTalent", "Naturalist", "OmenOfClarity", "MasterShapeshifter"]
	
	if sys.platform == "win32" or sys.platform == "win64":
		executable = "Branchsim.exe"
	else:
		executable = "./branchsim"
	
	numCores = detectCPUs()
	LoadConfigFile()
	
	while not quit:
		print "1. Load a Rawr character file"
		print "2. Display a config option"
		print "3. Edit a config option"
		print "4. Run the simulation"
		print "5. Determine stat weights (>= 100k fights recommended)"
		print ""
		print "9. Exit without saving"
		print "0. Save and exit"
		
		choice = raw_input("Enter a choice: ")
		if choice.isdigit() and int(choice) >= 0 and int(choice) <= 9:
			choice = int(choice)
			
			if choice == 0:
				print "Saving..."
				SaveConfigFile()
				quit = 1
			elif choice == 1:
				fileName = raw_input("Enter the complete file name: ")
				print "Loading..."
				ParseRawrFile(fileName)
			elif choice == 2:
				done = 0
				while not done:
					option = raw_input("Enter the option (or 'all', '0' to stop: ")
					if option == "0":
						done = 1
					elif option in config:
						print "\n" + option + ": " + config[option] + "\n"
						done = 1
					elif option.lower() == "all":
						PrintConfig()
						done = 1
					else:
						print "\nInvalid option, try again\n"
						sleep(1.5)
			elif choice == 3:
				done = 0
				while not done:
					option = raw_input("Enter the option (0 to stop): ")
					if option == "0":
						done = 1
					elif option in config:
						value = raw_input("Enter the value for " + option + ": ")
						if value != "":
							config[option] = value
							done = 1
						else:
							print "\nInvalid value, try again.\n"
							sleep(1.5)
					else:
						print "\nInvalid option, try again.\n"
						sleep(1.5)
			elif choice == 4:
				print "Saving..."
				SaveConfigFile()
				os.system(executable)
			elif choice == 5:
				print "Saving..."
				SaveConfigFile()
				FindStatWeights()
			elif choice == 6:
				print "Saving..."
				SaveConfigFile()
				FindOtherWeights()
			elif choice == 9:
				quit = 1
			else:
				print "\nInvaid choice, try again\n"
				
				sleep(1.5)
		else:
			option, value = choice.split('=')
			option = option.strip()
			value = value.strip()
			
			if option in config and value:
				config[option] = value
				print "\n" + option + " set to " + value + "\n"
			else:
				print "\nInvaid choice, try again\n"
			
			sleep(1.5)
	
	SaveConfigFile()


def FindStatWeights():
	global executable, configOrder
	
	stats = ["Str", "HasteRating", "ArPenRating", "HitRating", "CritRating", "AP", "WeaponAP", "Agi"]
	#stats = ["WeaponAP"]
	dps = {}
	thread = []
	
	for i in range(0, numCores):
		thread.append("")
	
	if len(stats) % numCores != 0:
		for i in range(0, numCores - (len(stats) % numCores)):
			stats.append("")
	
	sys.stdout.write("\rProgress: " + str(0.0) + "% <                                                  >")
	sys.stdout.flush()
	
	baseline = float(Popen([executable, "1"], stdout=PIPE).communicate()[0])
	
	sys.stdout.write("\rProgress: " + str(round(1.0 / (len(stats) + 1) * 100, 1)) + "% <                                                  >\rProgress: " + str(round(1.0 / (len(stats) + 1) * 100, 1)) + "% <")
	
	for i in range(0, int(round(1.0 / (len(stats) + 1) * 50, 0))):
		sys.stdout.write("=")
	
	sys.stdout.flush()
	
	sleep(1)
	
	for i in range(0, len(stats), numCores):
		for j in range(0, numCores):
			if stats[i + j] != "":
				thread[j] = Popen([executable, "1", stats[i + j], "-100"], stdout=PIPE)
		for j in range(0, numCores):
			if stats[i + j] != "":
				dps[stats[i + j]] = float(thread[j].communicate()[0])
		
		sys.stdout.write("\rProgress: " + str(round(float(i + 1 + numCores) / (len(stats) + 1) * 100, 1)) + "% <                                                  >\rProgress: " + str(round(float(i + 1 + numCores) / (len(stats) + 1) * 100, 1)) + "% <")
			
		for k in range(0, int(round(float(i + 1 + numCores) / (len(stats) + 1) * 50, 0))):
			sys.stdout.write("=")
		
		sys.stdout.flush()
	
	print ""
	
	for stat, value in dps.iteritems():
		dps[stat] = baseline - value
		if stat == "AP" or stat == "WeaponAP":
			dps[stat] /= 2.0
		
		dps[stat] /= 100.0
	
	if "AP" in dps:
		for stat, value in dps.iteritems():
			if stat != "AP":
				dps[stat] = round(dps[stat] / dps["AP"], 3)
		
		dps["AP"] = 1.000
	
	print "\nStat weights:"
	
	for stat, value in dps.iteritems():
		print stat + ": " + str(value)
	
	weightFileName = "Weights-" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".txt"
	
	print "\nSaving stat weights to " + weightFileName + "...\n"
	
	file = open(weightFileName, "w")
	
	for stat, value in dps.iteritems():
		file.write(stat + ": " + str(value) + "\n")
	
	file.write("\nConfig used:\n\n")
	
	for entry in configOrder:
		file.write(entry + ": " + config[entry] + "\n")
	
	file.close()
	
	sleep(1)

def FindOtherWeights():
	blank = ""

def detectCPUs():
	"""
	Detects the number of CPUs on a system. Cribbed from pp.
	"""
	# Linux, Unix and MacOS:
	if hasattr(os, "sysconf"):
		if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
			# Linux & Unix:
			ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
			if isinstance(ncpus, int) and ncpus > 0:
				return ncpus
			else: # OSX:
				return int(os.popen2("sysctl -n hw.ncpu")[1].read())
	# Windows:
	if os.environ.has_key("NUMBER_OF_PROCESSORS"):
		ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
		if ncpus > 0:
			return ncpus
	
	return 1 # Default

def PrintConfig():
	global config
	
	print ""
	
	for entry in configOrder:
		print entry + ": " + config[entry]
	
	print ""

def SaveConfigFile():
	global config
	
	file = open("branchsim.cfg", "w")
	
	for entry in configOrder:
		file.write(entry + ": " + config[entry] + "\n")
	
	file.close()
	

def LoadConfigFile():
	global config
	
	file = open("branchsim.cfg", "r")
	fileArray = file.read()
	fileArray = fileArray.split("\n")
	
	for line in fileArray:
		if line != "":
			temp = line.split(": ")
			config[temp[0]] = temp[1]
	
	file.close()
	

def GetColor(gem):
	global itemCache
	
	for node in itemCache.getElementsByTagName("Id"):
		if node.firstChild.nodeValue == gem:
			colorNode = node.parentNode.getElementsByTagName("Slot")
			color = colorNode[0].firstChild.nodeValue
			if color == "Green":
				return ["Yellow", "Blue"]
			elif color == "Purple":
				return ["Red", "Blue"]
			elif color == "Orange":
				return ["Red", "Yellow"]
			elif color == "Prismatic":
				return ["Red", "Yellow", "Blue"]
			else:
				return [color]
	
	return ""

def ParseRawrFile(fileName):
	global itemCache, config
	
	if not os.access(fileName, os.F_OK):
		print "\nUnable to access " + fileName + ", aborting\n"
		sleep(1)
		return
	
	character = xml.dom.minidom.parse(fileName)
	itemCache = xml.dom.minidom.parse("ItemCache.xml")
	enchantCache = xml.dom.minidom.parse("EnchantCache.xml")
	buffCache = xml.dom.minidom.parse("BuffCache.xml")
	
	rawrBuffs = []
	items = []
	enchants = []
	enchantSlots = []
	rawrTalents = []
	talents = {}
	rawrGlyphs = []
	glyphs = {}
	trinkets = []
	idols = {"RipIdol":0, "ShredIdol":0, "MangleIdol":0, "MutilationIdol":0}
	weaponEnchant = "None"
	rawrStats = {"Agility":0, "Strength":0, "CritRating":0, "HitRating":0, "ExpertiseRating":0, "ArmorPenetrationRating":0, "BonusCritMultiplier":0, "AttackPower":0, "HasteRating":0}
	stats = {"HasteRating":0, "ArPenRating":0, "HitRating":0, "ExpertiseRating":0, "CritRating":0, "AP":0, "WeaponAP":0, "Agi":0, "Str":0, "CritMeta":0}
	buffs = {"StrengthOfEarth":0, "ImpSoE":0, "BlessingOfMight":0, "ImpBoM":0, "TrueshotAura":0, "FerociousInspiration":0, "SwiftRetribution":0, "Windfury":0, "ImpWF":0, "MarkOfTheWild":0, "ImpMotWBuff":0, "BlessingOfKings":0, "HeroicPresence":0, "Sunder":0, "FaerieFire":0, "HeartOfTheCrusader":0, "BloodFrenzy":0, "Heroism":0, "MangleBot":0}
	
	config["TwoTSeven"] = "0"
	config["FourTSeven"] = "0"
	config["TwoTEight"] = "0"
	config["FourTEight"] = "0"
	config["TwoTNine"] = "0"
	config["FourTNine"] = "0"
	
	i = 0
	for node in character.getElementsByTagName("ActiveBuffs"):
		for node2 in node.childNodes:
			rawrBuffs.append("")
			if node2.nodeType == Node.TEXT_NODE:
				rawrBuffs[i] += node2.data
		
		i += 1
	
	for buff in rawrBuffs:
		if buff == "Strength of Earth Totem" or buff == "Horn of Winter":
			buffs["StrengthOfEarth"] = 1
		elif buff == "Enhancing Totems (Agility/Strength)":
			buffs["ImpSoE"] = 1
		elif buff == "Battle Shout" or buff == "Blessing of Might":
			buffs["BlessingOfMight"] = 1
		elif buff == "Commanding Presence (Attack Power)" or buff == "Improved Blessing of Might":
			buffs["ImpBoM"] = 1
		elif buff == "Trueshot Aura" or buff == "Unleashed Rage" or buff == "Abomination's Might":
			buffs["TrueshotAura"] = 1
		elif buff == "Ferocious Inspiration" or buff == "Sanctified Retribution":
			buffs["FerociousInspiration"] = 1
		elif buff == "Improved Moonkin Form" or buff == "Swift Retribution":
			buffs["SwiftRetribution"] = 1
		elif buff == "Windfury Totem":
			buffs["Windfury"] = 1
		elif buff == "Improved Windfury Totem" or buff == "Improved Icy Talons":
			buffs["Windfury"] = 1
			buffs["ImpWF"] = 1
		elif buff == "Mark of the Wild":
			buffs["MarkOfTheWild"] = 1
		elif buff == "Improved Mark of the Wild":
			buffs["ImpMotWBuff"] = 1
		elif buff == "Blessing of Kings":
			buffs["BlessingOfKings"] = 1
		elif buff == "Heroic Presence":
			buffs["HeroicPresence"] = 1
		elif buff == "Sunder Armor" or buff == "Expose Armor" or buff == "Acid Spit":
			buffs["Sunder"] = 1
		elif buff == "Curse of Recklessness" or buff == "Faerie Fire" or buff == "Sting":
			buffs["FaerieFire"] = 1
		elif buff == "Mangle" or buff == "Trauma":
			buffs["MangleBot"] = 1
		elif buff == "Heart of the Crusader" or buff == "Master Poisoner" or buff == "Totem of Wrath":
			buffs["HeartOfTheCrusader"] = 1
		elif buff == "Blood Frenzy" or buff == "Savage Combat":
			buffs["BloodFrenzy"] = 1
		elif buff == "Bloodlust":
			buffs["Heroism"] = 1
		elif buff == "Dreamwalker Battlegear 2 Piece Bonus":
			config["TwoTSeven"] = "1"
		elif buff == "Dreamwalker Battlegear 4 Piece Bonus":
			config["FourTSeven"] = "1"
		elif buff == "Nightsong Battlegear 2 Piece Bonus":
			config["TwoTEight"] = "1"
		elif buff == "Nightsong Battlegear 4 Piece Bonus":
			config["FourTEight"] = "1"
		elif re.compile("Food").search(buff) or re.compile("Feast").search(buff) or re.compile("Elixir").search(buff) or re.compile("Flask").search(buff):
			for node in buffCache.getElementsByTagName("Name"):
				if node.firstChild.nodeValue == buff:
					statsNode = node.parentNode.getElementsByTagName("Stats")
					print buff
					for child in statsNode[0].childNodes:
						if child.nodeType != Node.TEXT_NODE and child.nodeName in rawrStats:
							rawrStats[child.nodeName] += float(child.firstChild.nodeValue)
							print child.nodeName + child.firstChild.nodeValue
	
	talentNode = character.getElementsByTagName("DruidTalents")
	talentString, glyphString = talentNode[0].firstChild.nodeValue.split(".")
	
	for char in talentString:
		rawrTalents.append(str(char))
	
	talents["Ferocity"] = rawrTalents[28]
	talents["FeralAggression"] = rawrTalents[29]
	talents["SavageFury"] = rawrTalents[31]
	talents["FeralInstinct"] = rawrTalents[30]
	talents["SharpenedClaws"] = rawrTalents[35]
	talents["PrimalFury"] = rawrTalents[38]
	talents["PrimalPrecision"] = rawrTalents[39]
	talents["PredatoryStrikes"] = rawrTalents[37]
	talents["ShreddingAttacks"] = rawrTalents[36]
	talents["SurvivalOfTheFittest"] = rawrTalents[45]
	talents["HeartOfTheWild"] = rawrTalents[44]
	talents["LeaderOfThePack"] = rawrTalents[46]
	talents["PredatoryInstincts"] = rawrTalents[50]
	talents["Mangle"] = rawrTalents[53]
	talents["ImprovedMangle"] = rawrTalents[54]
	talents["KingOfTheJungle"] = rawrTalents[52]
	talents["RendAndTear"] = rawrTalents[55]
	talents["PrimalGore"] = rawrTalents[56]
	talents["Berserk"] = rawrTalents[57]
	talents["ImpMotWTalent"] = rawrTalents[58]
	talents["Naturalist"] = rawrTalents[61]
	talents["OmenOfClarity"] = rawrTalents[65]
	talents["MasterShapeshifter"] = rawrTalents[66]
	
	for char in glyphString:
		rawrGlyphs.append(str(char))
	
	glyphs["RoarGlyph"] = rawrGlyphs[4]
	glyphs["BerserkGlyph"] = rawrGlyphs[3]
	glyphs["RipGlyph"] = rawrGlyphs[2]
	glyphs["ShredGlyph"] = rawrGlyphs[1]
	glyphs["MangleGlyph"] = rawrGlyphs[0]
	
	for slot in ["Head", "Neck", "Shoulders", "Back", "Chest", "Wrist", "Hands", "Waist", "Legs", "Feet", "Finger1", "Finger2", "Trinket1", "Trinket2", "MainHand", "OffHand", "Ranged"]:
		for node in character.getElementsByTagName(slot):
			item = node.firstChild.nodeValue
			
			splitItem = item.split(".")
			
			items.append([splitItem[0]])
			
			if slot == "Trinket1" or slot == "Trinket2":
				if splitItem[0] == "40256":
					trinkets.append("GrimToll")
				elif splitItem[0] == "44253":
					trinkets.append("Greatness")
				elif splitItem[0] == "45522":
					trinkets.append("BloodOfTheOldGod")
				elif splitItem[0] == "45931":
					trinkets.append("MjolnirRunestone")
				elif splitItem[0] == "45609":
					trinkets.append("CometsTrail")
				elif splitItem[0] == "45263":
					trinkets.append("Wrathstone")
				elif splitItem[0] == "45286":
					trinkets.append("PyriteInfuser")
				elif splitItem[0] == "40684":
					trinkets.append("MirrorOfTruth")
				elif splitItem[0] == "44914":
					trinkets.append("AnvilOfTitans")
				elif splitItem[0] == "37390":
					trinkets.append("MeteoriteWhetstone")
				elif splitItem[0] == "40531":
					trinkets.append("MarkOfNorgannon")
				elif splitItem[0] == "39257":
					trinkets.append("LoathebsShadow")
				elif splitItem[0] == "37166":
					trinkets.append("SphereOfRedDragonsBlood")
				else:
					trinkets.append("None")
			elif slot == "Ranged":
				if splitItem[0] == "39757":
					idols["RipIdol"] = 1
				elif splitItem[0] == "40713":
					idols["ShredIdol"] = 1
				elif splitItem[0] == "45509":
					idols["MangleIdol"] = 1
			
			for i in range(1, 4):
				if splitItem[i] != "0":
					items[len(items) - 1].append(splitItem[i])
			
			if splitItem[4] != "0":
				if slot == "Finger1" or slot == "Finger2":
					enchantSlots.append("Finger")
				else:
					enchantSlots.append(slot)
				
				enchants.append(splitItem[4])
				
				if slot == "MainHand":
					if splitItem[4] == "3789":
						weaponEnchant = "Berserking"
					elif splitItem[4] == "2673":
						weaponEnchant = "Mongoose"
					elif splitItem[4] == "3225":
						weaponEnchant = "Executioner"
	
	stats["WeaponAP"] = 0
	
	for node in itemCache.getElementsByTagName("Id"):
		for item in items:
			for i in range(0, len(item)):
				if node.firstChild.nodeValue == item[i]:
					minDamageNode = node.parentNode.getElementsByTagName("MinDamage")
					if len(minDamageNode):
						maxDamageNode = node.parentNode.getElementsByTagName("MaxDamage")
						speedNode = node.parentNode.getElementsByTagName("Speed")
						stats["WeaponAP"] = round((((float(minDamageNode[0].firstChild.nodeValue) + float(maxDamageNode[0].firstChild.nodeValue)) / 2) / float(speedNode[0].firstChild.nodeValue) - 54.857) * 14, 0)
						rawrStats["AttackPower"] += stats["WeaponAP"]
					
					statsNode = node.parentNode.getElementsByTagName("Stats")
					for child in statsNode[0].childNodes:
						if child.nodeType != Node.TEXT_NODE and child.nodeName in rawrStats:
							rawrStats[child.nodeName] += float(child.firstChild.nodeValue)
							nameNode = node.parentNode.getElementsByTagName("Name")
							if len(minDamageNode) and child.nodeName == "AttackPower":
								stats["WeaponAP"] += float(child.firstChild.nodeValue)
					
					if i == 0:
						socketBonusAchieved = 1
						socketColor = ""
						for j in range(1, 4):
							socketColorNode = node.parentNode.getElementsByTagName("SocketColor" + str(j))
							if len(socketColorNode):
								socketColor = socketColorNode[0].firstChild.nodeValue
								
								if socketColor and (len(item) < j + 1 or not(str(socketColor) in GetColor(item[j]))):
									socketBonusAchieved = 0
							elif j == 1:
								socketBonusAchieved = 0
								j = 4
						
						if socketBonusAchieved:
							socketBonusNode = node.parentNode.getElementsByTagName("SocketBonus")
							if len(socketBonusNode) and socketBonusNode[0].hasChildNodes():
								for child in socketBonusNode[0].childNodes:
									if child.nodeType != Node.TEXT_NODE and child.nodeName in rawrStats:
										rawrStats[child.nodeName] += float(child.firstChild.nodeValue)
										if len(minDamageNode) and child.nodeName == "AttackPower":
											stats["WeaponAP"] += float(child.firstChild.nodeValue)
	
	for node in enchantCache.getElementsByTagName("Id"):
		for i in range(0, len(enchants)):
			if node.firstChild.nodeValue == enchants[i]:
				statsNode = node.parentNode.getElementsByTagName("Stats")
				slotNode = node.parentNode.getElementsByTagName("Slot")
				xmlSlot = slotNode[0].firstChild.nodeValue
				
				if xmlSlot == "TwoHand" or xmlSlot == "OneHand":
					xmlSlot = "MainHand"
				
				for child in statsNode[0].childNodes:
					if child.nodeType != Node.TEXT_NODE and str(xmlSlot) == str(enchantSlots[i]):
						if child.nodeName in rawrStats:
							rawrStats[child.nodeName] += float(child.firstChild.nodeValue)
							if child.nodeName == "AttackPower" and enchantSlots[i] == "MainHand":
								stats["WeaponAP"] += float(child.firstChild.nodeValue)
	
	for stat, value in rawrStats.iteritems():
		if stat == "Agility":
			stats["Agi"] = value
		elif stat == "Strength":
			stats["Str"] = value
		elif stat == "ArmorPenetrationRating":
			stats["ArPenRating"] = value
		elif stat == "BonusCritMultiplier" and value == 0.03:
			stats["CritMeta"] = 1
		elif stat == "AttackPower":
			stats["AP"] = value
		else:
			stats[stat] = value
	
	for stat, value in stats.iteritems():
		config[stat] = str(int(value))
	
	for buff, value in buffs.iteritems():
		config[buff] = str(value)
	
	for talent, value in talents.iteritems():
		config[talent] = str(value)
	
	for glyph, value in glyphs.iteritems():
		config[glyph] = str(value)
	
	for idol, value in idols.iteritems():
		config[idol] = str(value)
	
	if len(trinkets) >= 1:
		config["Trinket1"] = trinkets[0]
	else:
		config["Trinket1"] = "None"
	
	if len(trinkets) >= 2:
		config["Trinket2"] = trinkets[1]
	else:
		config["Trinket2"] = "None"
	
	config["WeaponEnchant"] = weaponEnchant

main()